using Godot;

namespace Arcane.Source.Utils
{
    public static class MathUtil
    {
        public static (int, int) MinMax(this (int l, int r) t)
        {
            return t.l > t.r ? (t.r, t.l) : (t.l, t.r);
        }

        public static Vector2I FloorToInt(this Vector2 v) => new(Mathf.FloorToInt(v.X), Mathf.FloorToInt(v.Y));
        
        public static Rect2 SquareByRadius(float halfSize) => new(-halfSize, -halfSize, halfSize * 2, halfSize * 2);
        public static Rect2 Square(float size) => SquareByRadius(0.5f * size);

        public static bool InRange(this int x, System.Range range)
        {
            return x >= range.Start.Value && x < range.End.Value;
        }

        public static Vector2 GetSizeRate(Vector2 size) => size / size.Dot(new(2, 2));

        public static void SampleCircle(Vector2[] buffer, float radius = 1)
        {
            var step = 2 * Mathf.Pi / buffer.Length;
            for (var i = 0; i < buffer.Length; i++)
            {
                var theta = i * step;
                buffer[i] = new Vector2(Mathf.Cos(theta), Mathf.Sin(theta)) * radius;
            }
        }

        public static Vector2[] SampleCircle(int sampleCount, float radius = 1)
        {
            Vector2[] points = new Vector2[sampleCount];
            SampleCircle(points, radius);
            return points;
        }

        public static Vector2 GetPointOnRect(Rect2 rect, float weight)
        {
            if (Mathf.IsZeroApprox(weight)) return rect.Position;
            // Make sure weight is between [0.0 1.0]
            weight -= Mathf.Floor(weight);

            var rate = GetSizeRate(rect.Size);

            var w = new Vector2(rect.Size.X, 0);
            var h = new Vector2(0, rect.Size.Y);

            var g = 4 * weight;

            float[] phase = [rate.Dot(Vector2.Right), rate.Dot(Vector2.One), rate.Dot(new(2, 1)), rate.Dot(new(2, 2))];
            
            if (weight <= phase[0])
                return rect.Position + g * w;
            else if (weight <= phase[1])
                return rect.Position + w + (g - 1) * h;
            else if (weight <= phase[2])
                return rect.Position + (3 - g) * w + h;
            else
                return rect.Position + (4 - g) * h;
        }

        public static Vector2 GetNextRectCorner(Rect2 rect, float weight)
        {
            if (Mathf.IsZeroApprox(weight)) return rect.Position;
            // Make sure weight is between [0.0 1.0]
            weight -= Mathf.Floor(weight);

            var rate = GetSizeRate(rect.Size);

            float[] phase = [rate.X, rate.X + rate.Y, rate.Dot(new(2, 1)), rate.Dot(new(2, 2))];
            
            if (weight < phase[0])
                return rect.Position + new Vector2(rect.Size.X, 0);
            else if (weight < phase[1])
                return rect.Position + rect.Size;
            else if (weight < phase[2])
                return rect.Position + new Vector2(0, rect.Size.Y);
            else
                return rect.Position;
        }    

        public static float ToMaximumIntMul(this float num, int target)
        {
            return Mathf.FloorToInt(num / target) * target;
        }
    }
}